package com.fabriciojf.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.*;

/**
 * classe para manipulacao de arquivos zip
 *
 * @author fabricio
 * @since 18/03/2010
 * @version 1.1 28/04/2010
 */
public class ZipCS {

    private File arquivoZipAtual;
    private static final int TAMANHO_BUFFER = 2048; // 2 Kb

    /**
     * Singleton
     */
    private static ZipCS instance = new ZipCS();
    public static ZipCS getInstance() {
        return instance;
    }

    /**
     * Retorna uma lista com o nome dos arquivos contidos no arquivo .zip
     * 
     * @param endereco do arquivo zip
     * @return lista de arquivos contidos no arquivo
     */
    public List<String> getContent(String arquivo) {

        ZipFile zip;
        List<String> arquivos = new ArrayList();

        try {
            zip = new ZipFile(arquivo);

            // pega o nome de todos os arquivos contidos no zip
            Enumeration e = zip.entries();
            while (e.hasMoreElements()) {
                ZipEntry entrada = (ZipEntry) e.nextElement();
                arquivos.add(entrada.getName());
            }
            zip.close();

        } catch (IOException ex) {
            Logger.getLogger(ZipCS.class.getName()).log(Level.SEVERE, null, ex);
        }

        return arquivos;
    }

    /**
     * Extrai o conteudo do arquivo zip
     *
     * @param arquivo zip
     * @param diretorio onde devera ser descompactado
     * @throws java.util.zip.ZipException
     * @throws java.io.IOException
     */
    public void extractZip(File arquivoZip, File diretorio)
            throws ZipException, IOException {
        ZipFile zip = null;
        File arquivo = null;
        InputStream is = null;
        OutputStream os = null;
        byte[] buffer = new byte[TAMANHO_BUFFER];
        try {
            //cria diretório informado, caso não exista
            if (!diretorio.exists()) {
                diretorio.mkdirs();
            }
            if (!diretorio.exists() || !diretorio.isDirectory()) {
                throw new IOException("Informe um diretorio valido");
            }
            zip = new ZipFile(arquivoZip);
            Enumeration e = zip.entries();
            while (e.hasMoreElements()) {
                ZipEntry entrada = (ZipEntry) e.nextElement();
                arquivo = new File(diretorio, entrada.getName());
                //se for diretório inexistente, cria a estrutura
                //e pula pra próxima entrada
                if (entrada.isDirectory() && !arquivo.exists()) {
                    arquivo.mkdirs();
                    continue;
                }
                //se a estrutura de diretórios não existe, cria
                if (!arquivo.getParentFile().exists()) {
                    arquivo.getParentFile().mkdirs();
                }
                try {
                    //lê o arquivo do zip e grava em disco
                    if (!entrada.isDirectory()) {
                        is = zip.getInputStream(entrada);
                        os = new FileOutputStream(arquivo);
                        int bytesLidos = 0;
                        if (is == null) {
                            throw new ZipException("Erro ao ler a entrada do zip: " + entrada.getName());
                        }
                        while ((bytesLidos = is.read(buffer)) > 0) {
                            os.write(buffer, 0, bytesLidos);
                        }
                    }
                } finally {
                    if (is != null) {
                        try {
                            is.close();
                        } catch (Exception ex) {
                        }
                    }
                    if (os != null) {
                        try {
                            os.close();
                        } catch (Exception ex) {
                        }
                    }
                }
            }
        } finally {
            if (zip != null) {
                try {
                    zip.close();
                } catch (Exception e) {
                }
            }
        }
    }

    /**
     * Cria um arquivo zip contendo uma lista de arquivos
     * 
     * @param arquivoZip que devera ser gerado
     * @param arquivos lista de arquivos que vao ser zipados
     * @return lista de arquivos do zip
     * @throws java.util.zip.ZipException
     * @throws java.io.IOException
     */
    public List createZip(File arquivoZip, File[] arquivos)
            throws ZipException, IOException {
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        setFileZipAtual(null);
        try {
            //adiciona a extensão .zip no arquivo, caso não exista
            if (!arquivoZip.getName().toLowerCase().endsWith(".zip")) {
                arquivoZip = new File(arquivoZip.getAbsolutePath() + ".zip");
            }
            fos = new FileOutputStream(arquivoZip);
            bos = new BufferedOutputStream(fos, TAMANHO_BUFFER);
            List listaEntradasZip = createZip(bos, arquivos);
            setFileZipAtual(arquivoZip);
            return listaEntradasZip;
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (Exception e) {
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (Exception e) {
                }
            }
        }
    }

    /**
     * Adiciona arquivos em um zip existente
     *
     * @param zos arquivo zip
     * @param arquivo a ser adicionado
     * @param caminhoInicial
     * @return lista de arquivos
     * @throws java.io.IOException
     */
    private List addFileInZip(ZipOutputStream zos, File arquivo,
            String caminhoInicial) throws IOException {
        List listaEntradasZip = new ArrayList();
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        byte buffer[] = new byte[TAMANHO_BUFFER];
        try {
            //diretórios não são adicionados
            if (arquivo.isDirectory()) {
                //recursivamente adiciona os arquivos dos diretórios abaixo
                File[] arquivos = arquivo.listFiles();
                for (int i = 0; i < arquivos.length; i++) {
                    List novasEntradas = addFileInZip(
                            zos, arquivos[i], caminhoInicial);
                    if (novasEntradas != null) {
                        listaEntradasZip.addAll(novasEntradas);
                    }
                }
                return listaEntradasZip;
            }
            String caminhoEntradaZip = null;
            int idx = arquivo.getAbsolutePath().indexOf(caminhoInicial);
            if (idx >= 0) {
                //calcula os diretórios a partir do diretório inicial
                //isso serve para não colocar uma entrada com o caminho completo
                caminhoEntradaZip = arquivo.getAbsolutePath().substring(idx + caminhoInicial.length() + 1);
            }
            ZipEntry entrada = new ZipEntry(caminhoEntradaZip);
            zos.putNextEntry(entrada);
            zos.setMethod(ZipOutputStream.DEFLATED);
            fis = new FileInputStream(arquivo);
            bis = new BufferedInputStream(fis, TAMANHO_BUFFER);
            int bytesLidos = 0;
            while ((bytesLidos = bis.read(buffer, 0, TAMANHO_BUFFER)) != -1) {
                zos.write(buffer, 0, bytesLidos);
            }
            listaEntradasZip.add(entrada);
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (Exception e) {
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (Exception e) {
                }
            }
        }
        return listaEntradasZip;
    }

    /**
     * Cria um arquivo zip
     * 
     * @param os saida do arquivo zip
     * @param arquivos lista de arquivos que serao zipados
     * @return lista de arquivos
     * @throws java.util.zip.ZipException
     * @throws java.io.IOException
     */
    public List createZip(OutputStream os, File[] arquivos) throws ZipException, IOException {
        if (arquivos == null || arquivos.length < 1) {
            throw new ZipException("Adicione ao menos um arquivo ou diretório");
        }
        List listaEntradasZip = new ArrayList();
        ZipOutputStream zos = null;
        try {
            zos = new ZipOutputStream(os);
            for (int i = 0; i < arquivos.length; i++) {
                String caminhoInicial = arquivos[i].getParent();
                List novasEntradas = addFileInZip(zos, arquivos[i], caminhoInicial);
                if (novasEntradas != null) {
                    listaEntradasZip.addAll(novasEntradas);
                }
            }
        } finally {
            if (zos != null) {
                try {
                    zos.close();
                } catch (Exception e) {
                }
            }
        }
        return listaEntradasZip;
    }

    /**
     * Fecha o arquivo zip em transacao
     */
    public void closeZip() {
        setFileZipAtual(null);
    }

    /**
     * Retorna o arquivo zip atual
     */
    public File getFileZipAtual() {
        return arquivoZipAtual;
    }

    /**
     * Seta o arquivo zip que devera ser trabalhado
     */
    private void setFileZipAtual(File arquivoZipAtual) {
        this.arquivoZipAtual = arquivoZipAtual;
    }

    /**
     * Comprime um diretorio inteiro incluindo seus diretorios
     *
     * @param dirOrigem diretorio a ser zipado
     * @param dirDestino diretorio para alocar o arquivo .zip
     * @param nomeSaida nome do arquivo zip na saida
     */
    public void compressDir(String dirOrigem, String dirDestino, String nomeSaida) {

        if (!FileCS.getInstance().getExt(nomeSaida).equals(".zip")) {
            nomeSaida = nomeSaida + ".zip";
        }

        try {
            File diretorio = new File(dirOrigem);
            File fList[] = diretorio.listFiles();

            if (fList != null) {
                createZip(new File(dirDestino + File.separator + nomeSaida), fList);
            }
        } catch (ZipException ex) {
            Logger.getLogger(ZipCS.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ZipCS.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /**
     * Pega os nomes dos arquivos contidos no zip e joga em uma lista
     * @param arq1 arquivo zip
     * @return lista de nomes dos arquivos contidos no zip
     * @throws java.util.zip.ZipException
     * @throws java.io.IOException
     */
    public List<String> getZipToList(File arq1) throws ZipException, IOException {

        ZipFile zip = null;

        InputStream is = null;
        OutputStream os = null;

        List<String> listaDeArquivos = new ArrayList<String>();
        try {

            zip = new ZipFile(arq1);
            Enumeration e = zip.entries();

            while (e.hasMoreElements()) {
                ZipEntry entrada = (ZipEntry) e.nextElement();
                listaDeArquivos.add(entrada.getName());
            }
        } finally {
            if (zip != null) {
                try {
                    zip.close();
                } catch (Exception e) {
                }
            }
        }

        return listaDeArquivos;
    }
}
